#pragma once

#include "Config.h"
#include <Windows.h>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
#include <iostream>
#include <memory>
#include <vector>
#include "DBException.h"


class DataBaseStatement;

enum CDataType
{
    Type_INVALID = 0,
    Type_CHAR,
    Type_UCHAR,
    Type_INTEGER,
    Type_UINTEGER,
    Type_STRING,
    Type_BINARY,
    Type_DATE,
    Type_TIME,
    Type_TIMESTAMP,
    Type_MAX,
};

static long Type2ODBCMap[Type_MAX] = 
{
    1,//Type_INVALID
    SQL_C_CHAR,//Type_CHAR,
    3,//Type_UCHAR,
    4,//Type_INTEGER,
    SQL_C_ULONG,//Type_UINTEGER,
    SQL_C_CHAR,//Type_STRING,
    7,//Type_BINARY,
    SQL_C_TYPE_DATE,//Type_DATE,
    SQL_C_TYPE_TIME,//Type_TIME,
    SQL_C_TYPE_TIMESTAMP,//Type_TIMESTAMP,
};

template< CDataType _TYPE, int _SIZE = 8 >
struct ResultCol
{
private:
    std::vector<char>       m_buffer;
    long                    m_indicator;       //The indicator
    CDataType                    m_type;

public:
    ResultCol()
        :m_buffer(_SIZE)
        ,m_indicator(0)
        ,m_type( _TYPE )
    {}

    ResultCol( size_t size )
        :m_buffer(size)
        ,m_indicator(0)
        ,m_type( _TYPE  )
    {}

    bool Resize( size_t size )
    {
        m_buffer.resize(size);
    }

    bool AdjustSize()
    {
        if( m_indicator > 0 && Size() < m_indicator )
        {
            return Resize( m_indicator + 1 );
        }
        return true;
    }

    size_t Size()
    {
        return m_buffer.size();
    }

    char* GetBuffer(){ return &m_buffer[0]; }

    long& GetIndicator(){ return m_indicator; }

    CDataType GetType(){ return m_type; }

    template< typename _OUT_TYPE >
    _OUT_TYPE GetValue(){ return *((_OUT_TYPE*)( &m_buffer[0] ));}

    template< typename _VAL_TYPE >
    void SetValue( _VAL_TYPE val ) { *( (_VAL_TYPE*)(&m_buffer[0] ) ) = val; }

    template< typename _OUT_TYPE >
    _OUT_TYPE* GetPointer(){ return reinterpret_cast<_OUT_TYPE*>(&m_buffer[0]); }

    void BindStatement( DataBaseStatement* statement, int columnIndex );
};

enum ParamIOType
{
    Param_Input = 0,
    Param_Output,
    Param_InputOutput,
    Param_Max
};

class StatementParam
{
//private:
public:
    std::vector<char>   m_buffer;
    long                m_strLenOrIndPtr; 
    long                m_sqlValueType;
    long                m_cDataType;
    long                m_columnSize; //Don't know too much about this
    ParamIOType         m_paramType;
    long                m_decimalDef;
public:
    StatementParam()
        :m_buffer(10)
        ,m_strLenOrIndPtr(0)
        ,m_sqlValueType(0)
        ,m_cDataType(0)
        ,m_columnSize(0)
        ,m_paramType( Param_Input )
        ,m_decimalDef( 0 )
    {}

    StatementParam( 
        ParamIOType paramType, 
        long cDataType, 
        long sqlValueType, 
        long columnSize = 0, 
        long decimalDef = 0 )

        :m_buffer(10)
        ,m_strLenOrIndPtr(0)
        ,m_sqlValueType( sqlValueType )
        ,m_cDataType( cDataType )
        ,m_columnSize( columnSize )
        ,m_paramType( paramType )
        ,m_decimalDef( decimalDef )
    {}

    size_t GetSize(){ return m_buffer.size(); }
    void   Resize( size_t size ){ m_buffer.resize( size ); }
    char*  GetBuffer() { return &m_buffer[0]; }

    template< typename _Ty >
    void   SetValue( _Ty val )
    {
        m_buffer.resize( sizeof( val ) );
        *( (_Ty*)GetBuffer() ) = val;
    }
};

class DataBaseStatement
{
public:
    
    DataBaseStatement()
        :m_statementHandle( SQL_NULL_HANDLE )
        ,m_prepared(false)
    {}

    DataBaseStatement( const wchar_t* statement )
        :m_statementHandle( SQL_NULL_HANDLE )
        ,m_statement( statement )
        ,m_prepared(false)
    {}

    DataBaseStatement( SQLHANDLE statementHandle, const wchar_t* statement )
        :m_statementHandle( statementHandle )
        ,m_statement( statement )
        ,m_prepared(false)
    {
    }

    void SetHandle( SQLHANDLE handle )
    {
        m_statementHandle = handle;
        m_prepared = false;
    }

    void SetStatement( const wchar_t* statement )
    {
        m_statement = statement;
        m_prepared = false;
    }

    std::wstring& GetStatement(){ return m_statement; }
    
    SQLHANDLE     GetHandle(){ return m_statementHandle; }

    void Prepare() throw( DataBaseException );

    void BindParameter ( int paramIndex, StatementParam* param )  throw( DataBaseException );


    void Execute() throw(DataBaseException);

    template< typename _ColType >
    void BindColumn( int columnIndex, _ColType& column )
    {
        SQLRETURN ret = SQLBindCol( GetHandle(), 
            columnIndex, 
            (SQLSMALLINT)Type2ODBCMap[ column.GetType() ], 
            column.GetBuffer(), 
            column.Size(), 
            &column.GetIndicator() 
            );
        if( CHECK_SQL_FAILED( ret ) )
        {
            throw DataBaseException( "Failed to bind column" );
        }
    }

    bool FetchPrev();
    
    bool FetchNext();

    //Will auto adjust the buffer
    bool FetchNextEx();

    bool FetchLast();

    void FetchEnd();

    int RowNumberAffected() throw( DataBaseException);
    int ColumnNumberRetured() throw( DataBaseException );

private:
    SQLHANDLE       m_statementHandle;
    std::wstring    m_statement;
    bool            m_prepared;
};


class DataBaseConnection
{
public:
    DataBaseConnection()
        :m_environmentHandle( SQL_NULL_HANDLE )
        ,m_connectionHandle( SQL_NULL_HANDLE )
        ,m_statementHandle(SQL_NULL_HANDLE )
    {}

    ~DataBaseConnection(){}

    void Initialize() throw( DataBaseException );
    void Connect( const char* serverName, const char* userName, const char* authentication ) throw( DataBaseException );
    void Connect( const wchar_t* serverName, const wchar_t* userName, const wchar_t* authentication ) throw( DataBaseException );
    void DisConnect() throw( DataBaseException );
    void UnInitialize() throw( DataBaseException );

    std::shared_ptr<DataBaseStatement> CreateStatement( const wchar_t* statement ) throw( DataBaseException );
    std::shared_ptr<DataBaseStatement> CreateStatement() throw( DataBaseException );

    void GetInfo( SQLUSMALLINT infoType, std::vector<char>& infoVal, bool resize = true ) throw( DataBaseException );
private:
    SQLHANDLE m_environmentHandle;
    SQLHANDLE m_connectionHandle;
    SQLHANDLE m_statementHandle;
};


template<CDataType _TYPE, int _SIZE>
void
ResultCol<_TYPE, _SIZE>::BindStatement( DataBaseStatement* statement, int columnIndex )
{
    SQLRETURN ret = SQLBindCol(statement->GetHandle(), 
            columnIndex, 
            (SQLSMALLINT)Type2ODBCMap[ GetType() ], 
            GetBuffer(), 
            Size(), 
            &GetIndicator() 
            );
    if( CHECK_SQL_FAILED( ret ) )
    {
         throw DataBaseException( "Failed to bind column" );
    }
}